package com.tonyodev.fetch2core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 计算类
 *
 * @since 2021-05-20
 */
public class AverageCalculator {
    /**
     * This value indicates how many values the value list can hold.
     * If a newer value is being add and the data list is full, the oldest value is deleted
     * and the newer value added. Works like a simple cache. The default value is 0 indicating
     * that values are not discarded.
     */
    private int discardLimit = 0;
    private int defaultValueListSize = 16;
    private int defaultIndexPosition = -1;

    /**
     * Linked list that holds values.
     */
    private double[] valueList = new double[defaultValueListSize];
    private int startIndex = defaultIndexPosition;
    private int endIndex = defaultIndexPosition;

    /**
     * 构造函数
     *
     * @param discardLimit discardLimit
     */
    public AverageCalculator(int discardLimit) {
        this.discardLimit = discardLimit;
    }

    /**
     * Add a new value to be calculated in the average. If the values list is equal to the discard limit
     * and the discard limit is greater than 0, the oldest value is discarded.
     *
     * @param value value to be calculated in average.
     */
    public void add(Double value) {
        if (discardLimit > 0 && count() == discardLimit) {
            startIndex += 1;
        }
        if (endIndex == valueList.length - 1) {
            expandValueList();
        }
        endIndex += 1;
        if (endIndex == 0) {
            startIndex = endIndex;
        }
        valueList[endIndex] = value;
    }

    /**
     * Doubles the value list size.
     */
    private void expandValueList() {
        double[] newList = new double[valueList.length * 2];
        int length = count();
        System.arraycopy(valueList, startIndex, newList, 0, length);
        valueList = newList;
        startIndex = 0;
        endIndex = length - 1;
    }

    /**
     * Clear all values.
     */
    public void clear() {
        valueList = new double[defaultValueListSize];
        startIndex = defaultIndexPosition;
        endIndex = defaultIndexPosition;
    }

    /**
     * Gets the number of values that are being held in the values list.
     *
     * @return number of items held in the values list.
     */
    public int count() {
        return (endIndex - startIndex) + 1;
    }

    /**
     * Gets the last input value or null if the values list is empty.
     *
     * @return last input value or null.
     */
    double getLastInputValue() {
        if (count() < 1) {
            throw new ArrayIndexOutOfBoundsException("value array is empty");
        }
        return valueList[endIndex];
    }

    /**
     * Gets the first input value or null if the values list is empty.
     *
     * @return first input value or null.
     */
    double getFirstInputValue() {
        if (count() < 1) {
            throw new ArrayIndexOutOfBoundsException("value array is empty");
        }
        return valueList[startIndex];
    }

    /**
     * Checks if the passed in value is in the values list.
     *
     * @param value value to check against.
     * @return true if the passed in value was found in the values list. False otherwise.
     */
    boolean hasInputValue(double value) {
        return Arrays.asList(valueList).contains(value);
    }

    /**
     * Gets all values contained in the values list.
     *
     * @return list of values contained in the values list.
     */
    public List<Double> getValues() {
        List<Double> list = new ArrayList<>();
        if (valueList != null) {
            for (int i = 0; i < valueList.length; i++) {
                list.add(valueList[i]);
            }
        }
        return list;
    }

    /**
     * Get the simple average of all values in the values list.
     *
     * @return simple value
     */
    public double getAverage() {
        double total = 0.0;
        for (int i = startIndex; i < endIndex + 1; i++) {
            total += valueList[i];
        }

        return total / count();
    }

    public double getMovingAverageWithWeightOnRecentValues() {
        return getMovingAverageWithWeightOnRecentValues(count());
    }

    /**
     * Gets the moving average with more weight being placed on recent values.
     *
     * @param inclusionCount the number of recent values to be included in the moving average calculation.
     * Default is all values in the values list
     * @return moving average with more weight placed on the recent values.
     */
    public double getMovingAverageWithWeightOnRecentValues(int inclusionCount) {
        if (inclusionCount < 1) {
            throw new IllegalArgumentException("inclusionCount cannot be less than 1.");
        }
        if (inclusionCount > count()) {
            throw new IllegalArgumentException("inclusionCount cannot be greater than the inserted value count.");
        }
        double movingAverage = 0.0;
        double weight = inclusionCount;
        double denominator = getDenominator(inclusionCount);
        if (denominator == 0) {
            denominator = 1;
        }
        for (int index = endIndex; index >= (endIndex - (inclusionCount - 1)); index--) {
            movingAverage += valueList[index] * (weight / denominator);
            --weight;
        }
        return movingAverage;
    }

    public double getMovingAverageWithWeightOnOlderValues() {
        return getMovingAverageWithWeightOnOlderValues(count());
    }

    /**
     * Gets the moving average with more weight being placed on the oldest values.
     *
     * @param inclusionCount the number of oldest values to be included in the moving average calculation.
     * Default is all values in the values list.
     * @return moving average with more weight placed on the the oldest values.
     */
    public double getMovingAverageWithWeightOnOlderValues(int inclusionCount) {
        if (inclusionCount < 1) {
            throw new IllegalArgumentException("inclusionCount cannot be less than 1.");
        }
        if (inclusionCount > count()) {
            throw new IllegalArgumentException("inclusionCount cannot be greater than the inserted value count.");
        }
        double movingAverage = 0.0;
        double weight = inclusionCount;
        double denominator = getDenominator(inclusionCount);
        if (denominator == 0) {
            denominator = 1;
        }
        for (int index = startIndex; index <= (startIndex + (inclusionCount - 1)); index++) {
            movingAverage += valueList[index] * (weight / denominator);
            --weight;
        }
        return movingAverage;
    }

    private double getDenominator(int number) {
        double total = 0.0;
        for (int i = 1; i < number + 1; i++) {
            total += i;
        }
        return total;
    }
}
